﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Windows.Threading;


namespace SpringEngine
{
    public class SpringEngine
    {
        //set to private
        public List<Spring> springs = new List<Spring>();

        private double friction = 0.1;
        /// <summary>
        /// Friction from 0-1
        /// </summary>
        public double Friction
        {
            get { return friction; }
            set
            {
                if (0 <= value && value <= 1)
                    friction = value;
                else
                    throw new ArgumentException("Friction should be from 0 to 1.");
            }
        }

        private DispatcherTimer timer;
        public event VectorsCalculated OnVectorsCalculated;
        public void InvokeOnVectorsCalculated()
        {
            VectorsCalculated handler = OnVectorsCalculated;
            if (handler != null) handler(Endpoints);
        }

        public delegate void VectorsCalculated(IEnumerable<Endpoint> endpoints);

        private HashSet<Endpoint> endpoints = new HashSet<Endpoint>();
        public HashSet<Endpoint> Endpoints
        {
            get { return endpoints; }
            set { endpoints = value; }
        }

        public SpringEngine()
        {
            timer= new DispatcherTimer();
            timer.Interval = TimeSpan.FromMilliseconds(1000/25.0);
            timer.Tick += new EventHandler(NextStep);
        }

        public void Start()
        {
            CalculateMomentVectors();
            timer.Start();
        }

        public void Stop()
        {
            timer.Stop();
        }

        void NextStep(object sender, EventArgs e)
        {
            SetEndpointsPositions();

            CalculateMomentVectors();
        }

        private void SetEndpointsPositions()
        {
            foreach (var endpoint in Endpoints)
            {
                var vectorX = endpoint.Acceleration.X;
                var vectorY = endpoint.Acceleration.Y;

                vectorX /= endpoint.Mass;
                vectorY /= endpoint.Mass;

                endpoint.Position = new Point(endpoint.Position.X + vectorX, endpoint.Position.Y - vectorY);

                var result = new Point(endpoint.Position.X, endpoint.Position.Y);

                endpoint.SetPosition(result);
            }
        }

        private void CalculateMomentVectors()
        {
            foreach (Endpoint endpoint in Endpoints)
                endpoint.Velocity = new Velocity();

            foreach (Spring spring in springs)
            {
                Endpoint endpoint1 = spring.Endpoints.First;
                Endpoint endpoint2 = spring.Endpoints.Second;

                double x1 = endpoint1.Position.X;
                double x2 = endpoint2.Position.X;
                double y1 = endpoint1.Position.Y;
                double y2 = endpoint2.Position.Y;

                double a = x2 - x1;
                double b = y2 - y1;
                double c = Math.Sqrt(a*a + b*b);

                MotionVector vector1 = new MotionVector();
                MotionVector vector2 = new MotionVector();

                if (c == spring.Length || c==0.0)
                { }
                else if (c < spring.Length)
                {
                    double vectorC = spring.Length-c;
                    double vectorA = vectorC * (a / c);
                    double vectorB = vectorC * (b / c);

                    vector1.X = -(vectorA / 2) * spring.Flexibility;
                    vector1.Y = +(vectorB / 2) * spring.Flexibility;
                    vector2.X = +(vectorA / 2) * spring.Flexibility;
                    vector2.Y = -(vectorB / 2) * spring.Flexibility;
                }
                else if (c > spring.Length)
                {
                    double vectorC = c-spring.Length;
                    double vectorA = vectorC * (a / c);
                    double vectorB = vectorC * (b / c);

                    vector1.X = +(vectorA / 2) * spring.Flexibility;
                    vector1.Y = -(vectorB / 2) * spring.Flexibility;
                    vector2.X = -(vectorA / 2) * spring.Flexibility;
                    vector2.Y = +(vectorB / 2) * spring.Flexibility;
                }

                endpoint1.Velocity.AddVector(vector1);
                endpoint2.Velocity.AddVector(vector2);
            }

            foreach (Endpoint endpoint in Endpoints)
            {
                endpoint.Acceleration += endpoint.Velocity;
                endpoint.Acceleration -= endpoint.Acceleration * Friction;
            }

            InvokeOnVectorsCalculated();
        }

        //public void AddSpring(ISpringable firstSpringable, ISpringable secondSpringable, double springLength)
        //{
        //    Endpoint firstEndpoint = Endpoint.CreateEndpoint(firstSpringable);
        //    Endpoint secondEndpoint = Endpoint.CreateEndpoint(secondSpringable);
        //    AddSpring(firstEndpoint,secondEndpoint,springLength);
        //}

        //public void AddSpring(Endpoint firstEndpoint, Endpoint secondEndpoint, double springLength)
        //{
        //    Spring spring = new Spring(firstEndpoint, secondEndpoint, springLength);
        //    AddSpring(spring);
        //}

        public void AddSpring(Spring spring)
        {
            springs.Add(spring);
            Endpoints.Add(spring.Endpoints.First);
            Endpoints.Add(spring.Endpoints.Second);
        }

        public void RemoveEndpoint(ISpringable endpoint)
        {
            List<Spring> springsToRemove = new List<Spring>();
            foreach (var spring in springs)
            {
                if (spring.Endpoints.First.EndpointObject == endpoint || spring.Endpoints.Second.EndpointObject == endpoint)
                    springsToRemove.Add(spring);
            }

            foreach (var spring in springsToRemove)
            {
                springs.Remove(spring);
            }
            Endpoints.RemoveWhere(e => e.EndpointObject == endpoint);
        }
    }

}
